﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Smile.Mvvm.Mediator
{
    /// <summary>
    ///     Allow differents objects to communicate by notifications and execute action when they
    ///     receive them. This implementation of the Mediator design pattern avoids memory leaks
    ///     due to references on objects and delagates.
    /// </summary>
    public class Messenger : IMessengerService
    {
        // @Public
        public Messenger()
        {
        }

        public void Register(string message, Action callback)
        {
            Register(message, callback, null);
        }

        public void Register<T>(string message, Action<T> callback)
        {
            Register(message, callback, typeof(T));
        }

        public void NotifyColleagues(string message)
        {
            if (string.IsNullOrEmpty(message))
            {
                throw new ArgumentException("Messenger: Message cannot be null or empty.");
            }

            Type registeredParameterType;
            if (_messageToActionsMap.TryGetParameterType(message, out registeredParameterType))
            {
                if (registeredParameterType != null)
                {
                    throw new TargetParameterCountException(
                        string.Format(
                            "Messenger: Must pass a parameter of type {0} with this message. Registered action(s) expect it.",
                            registeredParameterType.FullName));
                }
            }

            var actions = _messageToActionsMap.GetActions(message);
            if (actions != null)
            {
                while (actions.Count > 0)
                {
                    actions.Pop().DynamicInvoke();
                }
            }
        }

        public void NotifyColleagues(string message, object parameter)
        {
            if (string.IsNullOrEmpty(message))
            {
                throw new ArgumentException("Messenger: Message cannot be null or empty.");
            }

            Type registeredParameterType;
            if (_messageToActionsMap.TryGetParameterType(message, out registeredParameterType))
            {
                if (registeredParameterType == null)
                {
                    throw new TargetParameterCountException(
                        string.Format(
                            "Cannot pass a parameter with message '{0}'. Registered action(s) expect no parameter.",
                            message));
                }
            }

            var actions = _messageToActionsMap.GetActions(message);
            if (actions != null)
            {
                while (actions.Count > 0)
                {
                    actions.Pop().DynamicInvoke(parameter);
                }
            }
        }


        // @Private
        private readonly MessageToActionsMap    _messageToActionsMap = new MessageToActionsMap();


        private void Register(string message, Delegate callback, Type parameterType)
        {
            if (string.IsNullOrEmpty(message))
            {
                throw new ArgumentException("Messenger: \"message\" cannot be null or empty.");
            }

            if (callback == null)
            {
                throw new ArgumentNullException("Messenger: \"callback\" cannot be null.");
            }

            //VerifyParameterType(message, parameterType);
            _messageToActionsMap.AddAction(message, callback.Target, callback.GetMethodInfo(), parameterType);
        }

        [Conditional("DEBUG")]
        private void VerifyParameterType(string message, Type parameterType)
        {
            Type previouslyRegisteredParameterType = null;
            if (_messageToActionsMap.TryGetParameterType(message, out previouslyRegisteredParameterType))
            {
                if (previouslyRegisteredParameterType != null && parameterType != null)
                {
                    if (!previouslyRegisteredParameterType.Equals(parameterType.GetType()))
                    {
                        throw new InvalidOperationException(
                            string.Format(
                                "Messenger: The registered action's parameter type is inconsistent with the previously registered actions for message '{0}'.\nExpected: {1}\nAdding: {2}",
                                message,
                                previouslyRegisteredParameterType.FullName,
                                parameterType.FullName));
                    }
                }
                else
                {
                    if (previouslyRegisteredParameterType != parameterType)
                    {
                        throw new TargetParameterCountException(
                            string.Format(
                                "Messenger: The registered action has a number of parameters inconsistent with the previously registered actions for message \"{0}\".\nExpected: {1}\nAdding: {2}",
                                message,
                                previouslyRegisteredParameterType == null ? false : true,
                                parameterType == null ? false : true));
                    }
                }
            }
        }
    }
}
